﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using mqtt.client.test.Constant;
using mqtt.client.test.Mqtt;
using mqtt.client.test.Mqtt.Connect;
using mqtt.client.test.Mqtt.Subscribe;
using XjjXmm.Infrastructure.Common;
using mqtt.client.test.Mqtt.Constant;
using mqtt.client.test.Options;
using mqtt.client.test.Input;
using XjjXmm.Infrastructure.ToolKit;

namespace mqtt.client.test;

public class MqttClient : IDisposable
{
    private readonly MqttClientChannel _channel;

    public Action<Response<string>>? ConnAckAction { get; set; }

    public Action<Response<string>>? SubAckAction { get; set; }
    
 public Action<Response<string>>? PingAckAction { get; set; }

    public Action<Response<string>>? ReceiveMessageAction  { get; set; }
        
    public MqttClient(string host, int port)
    {
        _channel = new MqttClientChannel(host, port);

    }

    public async Task Connect(ConnectOption option)
    {
        await _channel.Connect(new ConnectInput(option));

       // var response = await _channel.ReceiveConnack();
       
        //await Ping(option.KeepAliveSecond);

        await Receive();

     

    }

    public async Task Publish(PublishOption option)
    {
        var input = new PublishInput(option);

        if (option.QoS == Qos.Qos0)
        {
            await _channel.SendPublish0(input);
        }
        else if (option.QoS == Qos.Qos1)
        {
            await _channel.SendPublish1(input);

        }
        else if (option.QoS == Qos.Qos2)
        {
            await _channel.SendPublish2(input);
        }
    }

    public async Task Subscribe(SubscribeOption option)
    {
        await _channel.SendSubscribe(new SubscribeInput(option));
        
    }


    private async Task Ping(int keepAlive)
    {
        await Task.Factory.StartNew(async () =>
        {
            while (true)
            {
                await Task.Delay(1000);
                await _channel.SendPingReq();
            }
        }, TaskCreationOptions.LongRunning);
    }

    private async Task Receive()
    {
        await Task.Factory.StartNew(async () =>
        { 
            await ReceiveMessage();
              
        }, TaskCreationOptions.LongRunning);
    }

    private Dictionary<int, string> _dictionary = new Dictionary<int, string>();
    private async Task ReceiveMessage()
    {
        var size = 512;
        var buff = new byte[size];
        ArraySegment<byte> buffer = new ArraySegment<byte>(buff);

        while (true)
        {
            var data = await _channel.Receive(buffer);
            
            var bit = data[0];
          //  var packetType = (bit >> 4);
           // var qos = (bit & 0x06) >> 1;
            switch (bit)
            {
                case (PacketType.CONNACK << 4) | HeaderFlag.CONNACK:
                    var connack = _channel.ReceiveConnack(data);
                    Console.WriteLine("connack: " + JsonKit.ToJson(connack));
                    ConnAckAction?.Invoke( connack);
                    break;
                case (PacketType.SUBACK << 4) | HeaderFlag.SUBACK:
                    // data.Dump("suback");
                    var suback = _channel.ReceiveSuback(data);
                    Console.WriteLine("suback: " + JsonKit.ToJson(suback));
                    
                    SubAckAction?.Invoke( suback);
                    break;
                case (PacketType.PUBACK << 4) | HeaderFlag.PUBACK: //qos1 
                    data.Dump("PUBACK");
                    
                    _channel.ReceivePubAck(data);
                    break;
                
                case (PacketType.PUBREC << 4) | HeaderFlag.PUBREC: //qos 2
                    data.Dump("PUBREC");                    
                    _channel.ReceivePubRec(data);
                    break;

                case (PacketType.PUBCOMP << 4) | HeaderFlag.PUBCOMP: // qos 2
                    data.Dump("PUBCOMP");                    
                    _channel.ReceivePubComp(data);
                    break;
                case PacketType.PUBLISH << 4 | HeaderFlag.PUBLISH:
                    data.Dump("publish");                    
                    var response0 = await _channel.ReceiveMessage(data);
                    ReceiveMessageAction?.Invoke(new Response<string>(response0.msg));
                    break;
                
                case PacketType.PUBLISH << 4 | Qos.Qos1 << 1:
                case PacketType.PUBLISH << 4 | Qos.Qos1 << 1 | 1 << 3:
                    data.Dump("publish1");                    
                    var response1 = await _channel.ReceiveMessage(data);
                    await _channel.SendPubAck(response1.packetIdentifier);
                    ReceiveMessageAction?.Invoke(new Response<string>(response1.msg));
                    break;
                
                case PacketType.PUBLISH << 4 | Qos.Qos2 << 1:
                case PacketType.PUBLISH << 4 | Qos.Qos2 << 1 | 1 << 3:
                    data.Dump("publish2");                    
                    var response2 = await _channel.ReceiveMessage(data);
                    await _channel.SendPubRec(response2.packetIdentifier);
                    _dictionary.Add(response2.packetIdentifier, response2.msg);
                    break;
                
                case (PacketType.PUBREL << 4) | HeaderFlag.PUBREL:
                    var pubRecResponse = _channel.ReceivePubRel(data);
                    var packetIdentifier = pubRecResponse.Result;
                    await _channel.SendPubComp(packetIdentifier);     
                    
                    ReceiveMessageAction?.Invoke(new Response<string>(_dictionary[packetIdentifier]));
                    _dictionary.Remove(packetIdentifier);
                    break;
    
                case (PacketType.PINGRESP << 4) | HeaderFlag.PINGRESP:
                    PingAckAction?.Invoke(_channel.ReceivePingResp(data));
                    break; 
                default:
                    data.Dump("default");
                    Console.WriteLine("----------------");
                    break;
            }
        }
    }

    public void Dispose()
    {
        _channel.Dispose();
    }
}